package com.spdd.mt.common.http;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.spdd.mt.common.base.BaseException;
import com.spdd.mt.common.http.exception.ConnectTimeoutException;
import com.spdd.mt.common.http.exception.ReadTimeoutException;
import com.spdd.mt.common.http.exception.UrlNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.*;

import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Component
public class HttpUtil {

    @Autowired
    private RestTemplate restTemplate;

    private static final HttpHeaders httpHeaders;
    private static final HttpHeaders httpFormHeaders;

    static {
        httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", "application/json;charset=utf-8");

        httpFormHeaders = new HttpHeaders();
        httpFormHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    }

    /**
     * Http post body 请求
     *
     * <p>
     * Backoff( delay = 1000,  // 初始延迟
     * maxDelay = 5000,       // 最大延迟
     * multiplier = 2,        // 延迟倍数
     * random = true          // 是否启用随机延迟
     * )
     *
     * @param url           请求地址
     * @param body          请求实体
     * @param typeReference 请求响应对象泛型类型
     */
    @Retryable(value = {
            ReadTimeoutException.class, ConnectTimeoutException.class, RestClientException.class
    },
            maxAttempts = 3, // 最大重试次数
            backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 5000)
    )
    public <T, B> T post(String url, B body, ParameterizedTypeReference<T> typeReference) {
        // url = host + url;
        Map<String, Object> bodyMap = BeanUtil.beanToMap(body);
        // bodyMap.put("app_key", appKey);
        // bodyMap.put("sign", sign(body));

        log.info("Http请求接口url={}, 请求参数 => {}", url, JSONUtil.toJsonStr(bodyMap));

        try {
            ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.POST,
                    new HttpEntity<>(bodyMap, httpHeaders),
                    typeReference);
            log.debug("Http请求返回数据 => {}", JSONUtil.toJsonStr(responseEntity));

            if (!org.springframework.http.HttpStatus.OK.equals(responseEntity.getStatusCode())) {
                throw new BaseException("Http请求异常, statusCode=" + responseEntity.getStatusCode());
            }

            T spResponse = responseEntity.getBody();
            if (Objects.isNull(spResponse)) {
                throw new BaseException("Http请求异常, body=null");
            }
            // if (!"0000".equals(spResponse.getCode())) { // 0000 表示失败
            //     throw new BaseException(spResponse.getCode(), spResponse.getMsg());
            // }
            return spResponse;
        } catch (BaseException e) {
            log.info("Http请求接口接异常, 请求参数={}，异常编码={}, 异常信息={}", JSONUtil.toJsonStr(body), e.getStatus(), e.getMessage());
            throw e;
        } catch (ResourceAccessException e) {
            // IO请求失败异常
            log.error("Http请求接口IO异常", e);
            if (e.getCause() instanceof UnknownHostException) {
                // 请求异常，网络不通
                log.info("请求异常，网络不通");
                throw new ConnectTimeoutException(e.getMessage());
            }
            if (e.getCause() instanceof SocketTimeoutException se) {
                if ("Read timed out".equalsIgnoreCase(se.getMessage())) {
                    log.info("响应异常");
                    throw new ReadTimeoutException(e.getMessage());
                }
                if ("Connect timed out".equalsIgnoreCase(se.getMessage())) {
                    log.info("请求异常");
                    throw new ConnectTimeoutException(e.getMessage());
                }
            }
            throw e;
        } catch (HttpClientErrorException e) {
            log.error("HttpClientErrorException 异常", e);
            if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
                // 404
                log.info("404 NOT FOUND");
                throw new UrlNotFoundException(e.getMessage());
            }
            throw e;
        } catch (RestClientResponseException e) {
            // 响应异常
            throw new ReadTimeoutException(e.getMessage());
        } catch (RestClientException e) {
            log.info("Http请求接口异常, 请求参数={}，异常信息={}", JSONUtil.toJsonStr(body), e.getMessage());
            log.error("Http请求接口异常", e);
            throw e;
        }
    }

    /**
     * Http post form 请求
     *
     * @param url           请求地址
     * @param body          请求实体
     * @param typeReference 请求响应对象泛型类型
     */
    public <T, B> T postForm(String url, B body, ParameterizedTypeReference<T> typeReference) {
        RestTemplate restTemplate = SpringUtil.getBean(RestTemplate.class);

        MultiValueMap<String, Object> multiBodyMap = new LinkedMultiValueMap<>();

        Map<String, Object> bodyMap = BeanUtil.beanToMap(body);
        bodyMap.forEach(multiBodyMap::add);

        log.info("Http请求接口url={}, 请求参数 => {}", url, JSONUtil.toJsonStr(multiBodyMap));

        try {
            ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.POST,
                    new HttpEntity<>(multiBodyMap, httpFormHeaders), typeReference);
            log.debug("Http请求返回数据 => {}", JSONUtil.toJsonStr(responseEntity));

            if (!org.springframework.http.HttpStatus.OK.equals(responseEntity.getStatusCode())) {
                throw new BaseException("Http请求异常, statusCode=" + responseEntity.getStatusCode());
            }

            T spResponse = responseEntity.getBody();
            if (Objects.isNull(spResponse)) {
                throw new BaseException("Http请求异常, body=null");
            }
            return spResponse;
        } catch (RestClientException e) {
            log.info("Http请求接口异常, 请求参数={}，异常信息={}", JSONUtil.toJsonStr(body), e.getMessage());
            log.error("Http请求接口异常", e);
            throw e;
        }
    }

}
